at main 5.0 kB view raw
1<script setup lang="ts"> 2import A from "@/components/content/ProseA.vue"; 3 4const config = useRuntimeConfig().public; 5const route = useRoute(); 6 7const { data: post } = await useAsyncData(`post-${route.path}`, () => 8 queryCollection("posts").path(route.path).first() 9); 10 11useSeoMeta({ 12 title: post.value?.title, 13 description: post.value?.description 14}); 15 16if (post.value) { 17 defineOgImageComponent("Post", { 18 title: post.value.title, 19 description: post.value.description, 20 date: post.value.date, 21 author: post.value.authors[0]?.name 22 }); 23} 24</script> 25 26<template> 27 <div 28 class="grow" 29 > 30 <NuxtLink 31 class="print:hidden flex items-center gap-1 text-sm text-neutral-600 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 mt-4" 32 to="/" 33 > 34 <Icon name="ri:arrow-drop-left-line" mode="svg" /> 35 Go back 36 </NuxtLink> 37 <article 38 v-if="post" 39 class="pb-24" 40 > 41 <header class="mb-8 mt-4"> 42 <h1 class="text-4xl font-bold"> 43 {{ post.title }} 44 </h1> 45 <div class="flex flex-wrap items-center justify-start gap-4 mt-4 text-neutral-600 dark:text-neutral-300"> 46 <div v-if="post.authors" class="flex items-center gap-1"> 47 <img v-if="post.authors[0]?.name === config.author" src="/logo.png" :alt="post.authors[0].name" 48 class="w-8 h-8 rounded-full mr-2"> 49 <span v-for="author in post.authors" :key="author.name"> 50 {{ author.name }} 51 </span> 52 </div> 53 · 54 <span> 55 {{ new Date(post.date).toLocaleDateString('en-GB', { 56 year: 'numeric', 57 month: 'long', 58 day: 'numeric' 59 }) }} 60 </span> 61 <span v-if="post.updated"> 62 <span class="text-neutral-500 dark:text-neutral-400 ml-2 mr-4">·</span> 63 <span class="text-sm"> 64 Updated: {{ new Date(post.updated).toLocaleDateString('en-GB', { 65 year: 'numeric', 66 month: 'long', 67 day: 'numeric' 68 }) }} 69 </span> 70 </span> 71 · 72 <div> 73 <span v-for="tag in post.tags" :key="tag" class="mr-2 mb-2 px-3 py-1 text-xs md:text-sm bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-400 rounded-full"> 74 {{ tag }} 75 </span> 76 </div> 77 </div> 78 79 <div class="bg-stone-200 dark:bg-stone-700 h-[1px] my-4"></div> 80 81 <p 82 class="text-md text-neutral-500 dark:text-neutral-400 leading-7 my-8 text-justify md:w-[80%] mx-auto" 83 > 84 {{ post.description }} 85 </p> 86 </header> 87 88 <div class="bg-stone-200 dark:bg-stone-700 h-[1px] my-8 md:w-[80%] mx-auto"></div> 89 90 <TableOfContents v-if="config.tableOfContents" :post="post" /> 91 92 <ContentRenderer :value="post" class="post-body prose prose-lg leading-7 prose-slate dark:prose-invert text-justify md:w-[80%] mx-auto text-zinc-800 dark:text-zinc-200" /> 93 94 <ShareActions :title="post.title" :description="post.description" :author="post.authors[0]?.name" /> 95 96 <Suspense> 97 <BskyComments v-if="post.bskyCid" :cid="post.bskyCid" /> 98 99 <template #fallback> 100 <h1 class="md:w-[80%] mx-auto mt-16 text-xl font-bold text-stone-600">Loading comments...</h1> 101 </template> 102 </Suspense> 103 104 </article> 105 106 <div v-else class="flex items-center justify-center"> 107 <article class="grid place-items-center gap-6 mt-12"> 108 <h2 class="text-3xl font-bold text-gray-900 dark:text-gray-200">It seems you might be lost...</h2> 109 <p class="text-gray-500 dark:text-gray-400">Please check the URL and try again.</p> 110 111 <div></div> 112 113 <div class="flex flex-row gap-2 items-baseline"> 114 <A href="/" target="_self" class="text-lg"> 115 Take me home!</A> 116 <span class="text-gray-500 dark:text-gray-400 text-sm">(country roads)</span> 117 </div> 118 </article> 119 </div> 120 </div> 121</template> 122 123<style> 124.post-body p:first-of-type::first-line { 125 font-weight: bold; 126} 127 128:target:before { 129 content: ""; 130 display: block; 131 height: 2rem; 132 margin: -2rem 0 0; 133} 134 135</style>